package com.googlecode.horncomb.web.spring.tags;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.jsp.JspException;

import net.sf.json.JSONObject;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.support.BindStatus;
import org.springframework.web.servlet.tags.form.AbstractFormTag;
import org.springframework.web.servlet.tags.form.TagWriter;

import com.googlecode.horncomb.util.lang.BeanUtils;
import com.googlecode.horncomb.web.util.TagUtils;

public class SecurityInputTag extends DynamicInputTag {
	private static final long serialVersionUID = 4087620791828899427L;

	public static final String PAGE_PERMISSION_KEY = "pagePermission";

	private static final Double defaultPermission = 9999d;
	protected String permission = null;
	protected String required = null;
	private AbstractFormTag innerTag = null;
	/**
	 * Marker object for items that have been specified but resolve to null.
	 * Allows to differentiate between 'set but null' and 'not set at all'.
	 */
	private static final Object EMPTY = new Object();

	private static HashSet<String> outputOnRead = new HashSet<String>();
	{
		outputOnRead.add("radio");
		outputOnRead.add("radioGroup");
		outputOnRead.add("checkbox");
		outputOnRead.add("checkboxGroup");
	}

	/**
	 * 初始js化校参数验
	 * @throws JspException
	 */
	protected void initValidationParameter() throws JspException {
		JSONObject jsonRoot = this.getMetadata();
		if (jsonRoot == null) {
			jsonRoot = new JSONObject();
		}
		this.buildValidationMetadata(jsonRoot);
		this.setMetadata(jsonRoot);
	}

	/**
	 * 构造所有校验的metadata
	 * @param validate
	 * @throws JspException
	 */
	protected void buildValidationMetadata(JSONObject jsonRoot)
			throws JspException {
		this.buildRequiredMetadata(jsonRoot);
	}

	/**
	 * 构造必填的metadata
	 * @param validate
	 * @throws JspException
	 */
	protected void buildRequiredMetadata(JSONObject jsonRoot)
			throws JspException {
		if (this.getRequired()) {
			JSONObject validate = new JSONObject();
			validate.put("required", true);
			extJSONObject(jsonRoot, "validate", validate, false);
			// jsonRoot.accumulate("validate", validate);
		}
	}

	/**
	 * 获取css中的metadata
	 * @return
	 * @throws JspException
	 */
	protected JSONObject getMetadata() throws JspException {
		String jsonStr = null;
		String css = this.resolveCssClass();
		JSONObject jsonRoot = null;
		if (StringUtils.hasText(css)) {
			int begin = css.indexOf('{');
			int end = css.lastIndexOf('}') + 1;
			if (begin > -1 && end > 0) {
				jsonStr = css.substring(begin, end);
				if (StringUtils.hasText(jsonStr)) {
					jsonRoot = JSONObject.fromObject(jsonStr);
				}
			}
		}
		return jsonRoot;
	}

	/**
	 * 重新设置css中的metadata
	 * @param jsonRoot
	 * @throws JspException
	 */
	protected void setMetadata(JSONObject jsonRoot) throws JspException {
		String css = this.resolveCssClass();
		// 清除原来数据
		if (StringUtils.hasText(css)) {
			int begin = css.indexOf('{');
			int end = css.lastIndexOf('}') + 1;
			if (begin > -1 && end > 0) {
				css = (StringUtils.trimWhitespace(css.substring(0, begin))
						+ " " + StringUtils.trimWhitespace(css.substring(end)));
			}
		}
		// 设置新值
		if (jsonRoot != null
				&& !jsonRoot.isNullObject()
				&& (!jsonRoot.entrySet().isEmpty() || !jsonRoot.keySet()
						.isEmpty())) {
			if (StringUtils.hasText(css)) {
				css = StringUtils.trimWhitespace(css) + " ";
			}
			css = css + jsonRoot.toString().replace("\"", "'");
		}
		this.setCssClass(css);
	}

	/**
	 * 安全的把值设置给json对象
	 * @param parentObj
	 * @param newObjKey
	 * @param newObj
	 * @param overwrite
	 */
	public static void extJSONObject(JSONObject parentObj, String newObjKey,
			Object newObj, boolean overwrite) {
		Assert.notNull(parentObj);
		Assert.hasText(newObjKey);
		// 获取旧值
		Object oldObj = parentObj.get(newObjKey);
		if (oldObj == null || overwrite) {
			parentObj.put(newObjKey, newObj);
			return;
		}
		if (oldObj != null) {
			if (oldObj instanceof JSONObject && newObj instanceof JSONObject) {
				newObj = extJSONObject((JSONObject) oldObj,
						(JSONObject) newObj, overwrite);
				parentObj.put(newObjKey, newObj);
			}
		}
	}

	/**
	 * 合并两个json对象
	 * @param oldObj
	 * @param newObj
	 * @param overwrite
	 * @return
	 */
	public static JSONObject extJSONObject(JSONObject oldObj,
			JSONObject newObj, boolean overwrite) {
		Assert.notNull(newObj);
		// 旧内容为空
		if (oldObj == null || oldObj.isNullObject()) {
			return newObj;
		} else
		// 新旧都有内容,准进行合并
		if (oldObj != null && !newObj.isNullObject()) {
			Iterator<?> ki = newObj.keys();
			while (ki.hasNext()) {
				Object k = (String) ki.next();
				Object oldVal = oldObj.get(k);
				Object newVal = newObj.get(k);
				if (overwrite || oldVal == null) {
					if (newVal == null
							|| (newVal instanceof String && "".equals(newVal))
							|| (newVal instanceof JSONObject && ((JSONObject) newVal)
									.isNullObject())) {
						oldObj.remove(k);
					} else {
						oldObj.put(k, newVal);
					}
				} else if ((oldVal instanceof JSONObject)
						&& (newVal != null && newVal instanceof JSONObject)) {
					extJSONObject((JSONObject) oldVal, (JSONObject) newVal,
							overwrite);
				}
			}
		}
		return oldObj;
	}

	/**
	 * 输出编辑形式的标签
	 * @param tagWriter
	 * @return
	 * @throws JspException
	 */
	protected int writeEditableTagContent(TagWriter tagWriter)
			throws JspException {
		this.initValidationParameter();
		return super.writeTagContent(tagWriter);
	}

	/**
	 * 输出所有值的隐藏域
	 * @param tagWriter
	 * @throws JspException
	 */
	protected void writeHiddenFields(TagWriter tagWriter) throws JspException {
		// 输出隐藏域
		Object boundValue = this.getBoundValue();
		Collection<?> valClt = null;
		if (boundValue != null) {
			if (boundValue.getClass().isArray()) {
				valClt = Arrays.asList(boundValue);
			} else if (boundValue instanceof Collection) {
				valClt = (Collection<?>) boundValue;
			} else if (boundValue instanceof Map) {
				valClt = ((Map<?, ?>) boundValue).keySet();
			}
		}
		if (valClt != null) {
			for (Object val : valClt) {
				this.writeHiddenFieldTagContent(tagWriter, val);
			}
		} else {
			this.writeHiddenFieldTagContent(tagWriter, boundValue);
		}
	}

	/**
	 * 输出显示形式的SPAN标签的内容
	 * @param tagWriter
	 * @return
	 * @throws JspException
	 */
	protected int writeVisitableSpanTagContent(TagWriter tagWriter)
			throws JspException {
		// 显示SPAN块
		SpanTag spanTag = new SpanTag();
		this.innerTag = spanTag;
		this.innerTag.setPageContext(this.pageContext);
		BeanUtils.copyPropertiesAsPossible(this, this.innerTag);
		spanTag.doStartTag();
		String label = this.getLabelContent();
		if (StringUtils.hasText(label)) {
			spanTag.getTagWriter().appendValue(label);
			return SKIP_BODY;
		} else {
			return EVAL_BODY_INCLUDE;
		}
	}

	protected String getLabelContent() throws JspException {
		String label = "";
		if ("select".equals(this.getType())) {
			Object items = this.getItems();
			if (items != null) {
				label = getSelectLabelContent(this.getItems());
			}
		} else {
			label = this.getDisplayString(this.getBoundValue(),
					this.getPropertyEditor());
		}
		return label;
	}

	/**
	 * 输出显示形式的标签
	 * @param tagWriter
	 * @return
	 * @throws JspException
	 */
	protected int writeVisitableTagContent(TagWriter tagWriter)
			throws JspException {
		this.initValidationParameter();
		// 输出隐藏域
		this.writeHiddenFields(tagWriter);
		if (outputOnRead.contains(this.getType())) {
			// radio/checkbox
			this.setDisabled(Boolean.TRUE.toString());// 只读状态
			return super.writeTagContent(tagWriter);
		} else {
			return this.writeVisitableSpanTagContent(tagWriter);
		}
	}

	/**
	 * 输出隐藏形式的标签
	 * @param tagWriter
	 * @return
	 * @throws JspException
	 */
	protected int writeHiddenTagContent(TagWriter tagWriter)
			throws JspException {
		this.writeHiddenFields(tagWriter);
		return SKIP_BODY;
	}

	@Override
	protected int writeTagContent(TagWriter tagWriter) throws JspException {
		return this.writeSecurityInputTagContent(tagWriter);
	}

	protected int writeSecurityInputTagContent(TagWriter tagWriter)
			throws JspException {
		double pms = this.getPermission();
		if (pms > 0) {
			// 编辑
			return this.writeEditableTagContent(tagWriter);
		} else if (pms == 0) {
			// 显示
			return this.writeVisitableTagContent(tagWriter);
		} else {
			// 隐藏
			return this.writeHiddenTagContent(tagWriter);
		}
	}

	protected String getSelectLabelContent(Object items) throws JspException {
		String valueProperty = (getItemValue() != null ? ObjectUtils
				.getDisplayString(this.evaluate("itemValue", getItemValue()))
				: null);
		String labelProperty = (getItemLabel() != null ? ObjectUtils
				.getDisplayString(this.evaluate("itemLabel", getItemLabel()))
				: null);
		String result = "";
		if (items != null) {
			// Items specified, but might still be empty...
			if (items != EMPTY) {
				Object itemsObject = (items instanceof String ? evaluate(
						"items", (String) items) : items);
				if (itemsObject != null) {
					if (items.getClass().isArray()) {
						result = this.doGetSelectLabelFromCollection(
								Arrays.asList(items), valueProperty,
								labelProperty);
					} else if (items instanceof Collection) {
						result = this.doGetSelectLabelFromCollection(
								(Collection<?>) items, valueProperty,
								labelProperty);
					} else if (items instanceof Map) {
						result = this
								.doGetSelectLabelFromMap((Map<?, ?>) items,
										valueProperty, labelProperty);
					} else {
						throw new JspException("Type ["
								+ items.getClass().getName()
								+ "] is not valid for option items");
					}
				}
			}
		}
		return result;
	}

	/**
	 * 从Map获取选项名称
	 * @param optionMap
	 * @param labelProperty
	 * @param valueProperty
	 * @return
	 * @throws JspException
	 */
	private String doGetSelectLabelFromMap(Map<?, ?> optionMap,
			String labelProperty, String valueProperty) throws JspException {
		for (Iterator<?> it = optionMap.entrySet().iterator(); it.hasNext();) {
			Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
			String selectedLable = doGetSelectedLabel(entry, entry.getValue(),
					entry.getKey());
			if (StringUtils.hasText(selectedLable)) {
				return selectedLable;
			}
		}
		return "";
	}

	/**
	 * 从集合获取选项名称
	 * @param optionCollection
	 * @param valueProperty
	 * @param labelProperty
	 * @return
	 * @throws JspException
	 */
	protected String doGetSelectLabelFromCollection(
			Collection<?> optionCollection, String valueProperty,
			String labelProperty) throws JspException {
		for (Iterator<?> it = optionCollection.iterator(); it.hasNext();) {
			Object item = it.next();
			BeanWrapper wrapper = new BeanWrapperImpl(item);
			Object value = (valueProperty != null ? wrapper
					.getPropertyValue(valueProperty) : item);
			Object label = (labelProperty != null ? wrapper
					.getPropertyValue(labelProperty) : item);
			String selectedLable = doGetSelectedLabel(item, label, value);
			if (StringUtils.hasText(selectedLable)) {
				return selectedLable;
			}
		}
		return "";
	}

	/**
	 * 判断是否选中,并返回选中项的名称
	 * @param item
	 * @param label
	 * @param value
	 * @return
	 * @throws JspException
	 */
	protected String doGetSelectedLabel(Object item, Object label, Object value)
			throws JspException {
		try {
			Class<?> cmpCls = ClassUtils
					.forName(
							"org.springframework.web.servlet.tags.form.SelectedValueComparator",
							this.getClass().getClassLoader());
			Method method = cmpCls.getMethod("isSelected", new Class[] {
					BindStatus.class, Object.class });
			if (cmpCls.getModifiers() != Modifier.PUBLIC
					|| method.getModifiers() != Modifier.PUBLIC) {
				method.setAccessible(true);
			}
			boolean selected = (Boolean) method.invoke(null, new Object[] {
					this.getBindStatus(), value })
					|| (Boolean) method.invoke(null,
							new Object[] { this.getBindStatus(), item });
			if (selected) {
				return this.getDisplayString(label);
			} else {
				return "";
			}
		} catch (Exception e) {
			throw new JspException(e);
		}
	}

	/**
	 * 输出隐藏域标签
	 * @param tagWriter
	 * @param value
	 * @throws JspException
	 */
	protected void writeHiddenFieldTagContent(TagWriter tagWriter, Object value)
			throws JspException {
		tagWriter.startTag("input");
		writeDefaultAttributes(tagWriter);
		tagWriter.writeAttribute("type", "hidden");
		tagWriter.writeAttribute("value",
				getDisplayString(value, getPropertyEditor()));
		tagWriter.endTag();
	}

	/**
	 * @param permission
	 *            the permission to set
	 */
	public void setPermission(String permission) throws NumberFormatException,
			JspException {
		this.permission = permission;
	}

	protected String getPermissionDisplay() throws NumberFormatException,
			JspException {
		String pms = this.getDisplayString(this.evaluate("permission",
				this.permission));
		if (!StringUtils.hasText(pms)) {
			Object pagePrmsObj = this.pageContext
					.getAttribute(PAGE_PERMISSION_KEY);
			if (pagePrmsObj != null) {
				pms = pagePrmsObj.toString();
			}
		}
		return pms;
	}

	/**
	 * @return
	 * @throws NumberFormatException
	 * @throws JspException
	 */
	protected Double getPermission() throws NumberFormatException, JspException {
		String pms = this.getPermissionDisplay();
		if (!StringUtils.hasText(pms)) {
			return defaultPermission;
		} else {
			return Double.parseDouble(pms);
		}
	}

	/**
	 * @param required
	 *            the required to set
	 */
	public void setRequired(String required) {
		this.required = required;
	}

	protected boolean getRequired() throws JspException {
		return TagUtils.getBooleanValue("required", this.required, pageContext);
	}

	//
	// protected String getName() throws JspException {
	// if (this.getPermission() > 0) {
	// return getCompletePath();
	// } else {
	// return "";
	// }
	// }

	@Override
	public int doAfterBody() throws JspException {
		if (this.innerTag != null) {
			return this.innerTag.doAfterBody();
		} else {
			return super.doAfterBody();
		}
	}

	@Override
	public int doEndTag() throws JspException {
		int rs = 0;
		if (this.innerTag != null) {
			rs = this.innerTag.doEndTag();
			this.innerTag = null;
		} else {
			rs = super.doEndTag();
		}
		this.setCssClass(null);
		return rs;
	}

	@Override
	public void release() {
		super.release();
		this.setCssClass(null);
	}

	@Override
	public void doFinally() {
		if (this.innerTag != null) {
			this.innerTag.doFinally();
		}
		super.doFinally();
		this.innerTag = null;
	}
}
